package it.unicam.cs.cosy.bioshape.core.components;

import hermesV3.Message;
import hermesV3.agent.Identifier;
import hermesV3.agent.InteractionContainer;
import hermesV3.agent.UserAgent;
import hermesV3.protocols.SplitSpaceProtocol;
import hermesV3.protocols.StepProtocol;
import hermesV3.protocols.SynProtocol;
import hermesV3.services.HubAgent;
import hermesV3.services.HubService.PeerAgent;
import hermesV3.services.HubService.SimulationAgent;
import hermesV3.services.Space.SpaceManager;
import hermesV3.util.HermesLogger;
import it.unicam.cs.cosy.bioshape.core.components.equilibriums.EquilibriumCondition;
import it.unicam.cs.cosy.bioshape.core.util.Debug;
import it.unicam.cs.cosy.bioshape.core.util.Messages;
import it.unicam.cs.cosy.bioshape.core.util.ObjShape;
import it.unicam.cs.cosy.bioshape.core.util.XmlReader;
import it.unicam.cs.cosy.bioshape.gui.visualizer3d.Command;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;

import java.util.Hashtable;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

import javax.swing.JOptionPane;

/**
 * Supervises the simulation. It has several responsibilities during the
 * simulation:
 * <ul>
 * <li>manages the {@code PeerAgent} agents negotiation;</li>
 * <li>register the {@code LocalCoordinator} agents for the simulation;</li>
 * <li>decides (along with the SpaceManager) which is the shortest time of
 * simulation for the current iteration (see {@code #computeRealStep()};</li>
 * </ul>
 * 
 * @author Federico Buti, Gaston Alanis, Matteo Micheletti, Andrea Piermarteri
 */
public class GlobalCoordinator extends UserAgent {

    private static final long serialVersionUID = 7096924436996619242L;

    private double time;

    private XmlReader xmlInputFile;

    private double delta = 1; // default value is 1!

    private ArrayList<Identifier> peerList = new ArrayList<Identifier>();

    Hashtable<Identifier, Double> steps;

    private Identifier spaceId;

    private double maxTranslation;

    private double minRadius;

    private EquilibriumCondition eq;

    private boolean termination;

    private Identifier inputId;

    private int timeout;

    private File simFile;

    /**
     * Generates the {@code GlobalCoordinator}.
     * 
     * @param name
     *            Name (from the class name) of the {@code GlobalCoordinator}.
     * @param inputId
     *            Agents controlling the GUI.
     * 
     * @see Identifier is a reference to the {@code SimulationAgent} of the node
     *      which interact with the simulator GUI.
     * 
     * 
     * @param xmlInputFile
     *            The XML input used to set up simulation.
     * @param timeout
     *            Temporal timeout in {@code PeerAgent}/{@code LocalCoordinator}
     *            agents connections to the {@code GlobalCoordinator}. Passed in
     *            seconds, it is multiplied by 1000 to met the internal Java
     *            representation in milliseconds.
     * @see SimulationAgent
     */
    public GlobalCoordinator(String name, XmlReader xmlInputFile,
            Integer timeout, Identifier inputId) {
        super(name);
        this.xmlInputFile = xmlInputFile;
        this.eq = xmlInputFile.getEq();
        this.timeout = timeout.intValue();
        this.timeout *= 1000;
        this.inputId = inputId;
    }

    /**
     * Sets-up topology and pre-simulation tasks. 0-3 are initialisation tasks:
     * peers and locals acknowledgement and creation of support structures; 4-6
     * are pre-simulation tasks in which the GC interact with the SM for setting
     * all the data for the simulation phase; finally, 8 is the simulation phase
     * which the simulation cycle which is executed until the chosen
     * {@code EquilibriumCondition} is satisfied.
     */
    @Override
    public void init() {
        reception();
        HermesLogger.log(HermesLogger.INFO, this.getAgentName()
                + " Coordinator up and running!!");
        // 0. communicates with the HUB for SpaceManager ID...
        Message<?> msg = Messages.receiveSyn(this, HubAgent.class);
        if (msg == null) {
            HermesLogger.log(HermesLogger.ERROR, this.getAgentName()
                    + " something wrong with the communication "
                    + "with the HUB...Simulation stopped!");
            terminate();
            return;
        }
        // else...
        Identifier hubId = msg.getSenderAgentId();
        InteractionContainer ic = (InteractionContainer) msg.getObject();
        spaceId = (Identifier) ic.getParameter(SynProtocol.INTERACTIONSYN);
        //
        // 1. retrieve locals number from input...
        int numLocals = 0;
        numLocals = xmlInputFile.getLocals();
        //
        // 2. receives acks from the Peers...
        try {
            receiveAckWithTimeout(PeerAgent.class, peerList, numLocals, timeout);
        } catch (InterruptedException ie) {
            // exception never launched --> normally the "invokeAll()" which
            // generates it MUST not be interrupted!!
            ie.printStackTrace();
        }
        // Something wrong ---> termination = true
        if (termination) {
            terminate();
            return;
        }
        // ...and sends them the syn(s)....
        sendSyn(peerList);
        //
        // 3. receives acks from the Locals and create support structures...
        ArrayList<Identifier> localCoordList = new ArrayList<Identifier>();
        try {
            receiveAckWithTimeout(LocalCoordinator.class, localCoordList,
                    peerList.size(), timeout);
        } catch (InterruptedException ie) {
            // same as the previous one...
            ie.printStackTrace();
        }
        if (termination) {
            terminate();
            return;
        }
        steps = new Hashtable<Identifier, Double>(localCoordList.size());
        for (Identifier id : localCoordList)
            steps.put(id, new Double(-1));
        HermesLogger.log(HermesLogger.INFO, this.getAgentName() + " - All the "
                + localCoordList.size() + " LocalCoordinators ready to start "
                + "simulation...");
        //
        //
        // 4. Sets up the simulation on the basis of input
        HermesLogger.log(HermesLogger.INFO, this.getAgentName()
                + " Setting up simulation from input...");
        // msg to SpaceManager -> startDivision
        ic = new InteractionContainer(new SplitSpaceProtocol());
        ic.makeInteraction(SplitSpaceProtocol.INTERACTIONSENDIDS,
                localCoordList);
        Messages.sendInteraction(this, spaceId, ic);
        // 5. Create support file: simulation and geometry files. They have same
        // path of the XML input file but, obviously, different extension
        File file = xmlInputFile.getFile();
        String path = file.getAbsolutePath();
        path = path.substring(0, path.indexOf(".xml"));
        simFile = new File(path + ".simu");
        File geomFile = new File(path + ".geom");
        try {
            if (simFile.exists())
                simFile.delete();
            simFile.createNewFile();
            if (geomFile.exists())
                geomFile.delete();
            geomFile.createNewFile();
            createGeometryFile(geomFile);
        } catch (IOException e) {
            HermesLogger.log(HermesLogger.ERROR, this.getClass()
                    .getSimpleName()
                    + " - Impossible to create the simulation"
                    + " log file. The visualiser cannot be used.");
        }
        receiveLogs(true);
        // 6. wait an ack from the SpaceManager
        if (Messages.receiveAck(this, spaceId) == null) {
            HermesLogger.log(HermesLogger.WARNING, this.getAgentName()
                    + " - protocol error while "
                    + "communicating with the GLOBAL");
            terminate();
            return;
        }
        HermesLogger.log(HermesLogger.INFO, this.getAgentName()
                + " - SpaceManager correctly received the simulation"
                + " information, waiting setup....");
        //
        // 7. set up the MOST important information about the simulation
        this.maxTranslation = xmlInputFile.getSpatialDelta();
        this.minRadius = calculateMinRadius();
        delta = xmlInputFile.getTemporaldelta();
        if (delta == 0)
            computeDelta(maxTranslation, minRadius);
        // 8. receive ACK from Space --> set up done
        if (Messages.receivedInteraction(this, spaceId,
                SplitSpaceProtocol.INTERACTIONDIVISIONDONE) == null) {
            HermesLogger.log(HermesLogger.ERROR, this.getAgentName()
                    + " something wrong with the communication "
                    + "with the SpaceManager...Simulation stopped!");
            terminate();
            return;
        }
        // 9. finalizing --> destroying HUB
        boolean res = Messages.sendServiceInteraction(this, hubId,
                new SynProtocol(), SynProtocol.INTERACTIONACK);
        if (!res) {
            HermesLogger.log(HermesLogger.ERROR, this.getAgentName()
                    + " something wrong with the communication "
                    + "with the HUB Service...Simulation stopped!");
            terminate();
            return;
        }
        //
        // 10. now the simulation can really START!
        simulationMainLoop();
    }

    /**
     * Reads the 3D shapes of the input file and generates the {@code .geom}
     * file containing the forms in {@code Obj} format.
     * 
     * @param geomFile
     *            The XML input file.
     * @throws IOException
     *             Thrown if an error occurs while writing the file.
     */
    private void createGeometryFile(File geomFile) throws IOException {
        BufferedWriter bw = new BufferedWriter(new FileWriter(geomFile));
        for (ObjShape oS : xmlInputFile.getObjShapes()) {
            bw.append(oS.toString() + "\n\n\n");
        }
        bw.append("# Simulation set-up data for input file '"
                + geomFile.getName().substring(0,
                        geomFile.getName().indexOf(".g")) + ".xml' \n");
        bw.append("# geometry to shape id mapping\n");
        bw.append("# min " + xmlInputFile.getSimulationBounds()[0].x + " "
                + xmlInputFile.getSimulationBounds()[0].y + " "
                + xmlInputFile.getSimulationBounds()[0].z + "\n");
        bw.append("# max " + xmlInputFile.getSimulationBounds()[1].x + " "
                + xmlInputFile.getSimulationBounds()[1].y + " "
                + xmlInputFile.getSimulationBounds()[1].z + "\n");
        String split = "# split ";
        if (xmlInputFile.getCuttingAxis() == SpaceManager.SPLITX)
            split = split + "x";
        else
            split = xmlInputFile.getCuttingAxis() == SpaceManager.SPLITY ? split
                    + "y"
                    : split + "z";
        //
        bw.append(split + "\n");
        bw.append("# offset " + xmlInputFile.getOffset() + "\n");
        bw.append("# locals " + steps.keySet().size() + "\n");
        bw.flush();
        bw.close();
    }

    /**
     * Receives all the {@code LocalCoordinator}s logs about the entities.
     * 
     * @param flag
     *            {@code true} if this is the first log and the header must be
     *            added to the serialized file, {@code false} otherwise
     * 
     * @see AppendableObjectOutputStream
     * */
    @SuppressWarnings({ "unchecked" })
    private boolean receiveLogs(boolean flag) {
        int counter = steps.size();
        ObjectOutputStream o = null;
        FileOutputStream fos = null;
        // synchronized (Writer.class) { errore!!
        try {
            fos = new FileOutputStream(simFile, true);
            o = flag ? new ObjectOutputStream(fos)
                    : new AppendableObjectOutputStream(fos);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        // }
        ArrayList<Command> commands = new ArrayList<Command>();
        // TODO fare con IDS e error
        while (counter > 0) {
            Message<?> msg = Messages.receivedInteraction(this,
                    LocalCoordinator.class, StepProtocol.INTERACTIONSENDSTATUS);
            if (msg == null) {
                HermesLogger.log(HermesLogger.ERROR, this.getAgentName()
                        + " something wrong with the communication "
                        + "with the Locals...Simulation stopped!");
                try {
                    if (o != null)
                        o.close();
                    if (fos != null)
                        fos.close();

                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                return false;
            }
            InteractionContainer ic = (InteractionContainer) msg.getObject();
            ArrayList<Command> cmd = (ArrayList<Command>) ic
                    .getParameter("SendStatus");
            commands.addAll(cmd);
            counter--;
        }
        // new Writer(o, fos, commands).run();
        try {
            if (o == null || fos == null)
                return false;
            for (Command c : commands) {
                o.writeObject(c);
            }
            fos.close();
            o.close();
        } catch (IOException e) {
            return false;
        }
        return true;
    }

    /**
     * Adds a {@code null} object to the serialized file to signal termination
     * of the visual log.
     */
    private void closeLog() {
        ObjectOutputStream o = null;
        // synchronized (Writer.class) {
        try {
            FileOutputStream fos = new FileOutputStream(simFile, true);
            o = new AppendableObjectOutputStream(fos);
            o.writeObject(null);
            fos.close();
            o.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        // }
    }

    /**
     * Executed in parallel with the {@code GlobalCoordinator}. Stores the
     * {@code Entity} object data to feed the visualiser.
     * 
     * @author Federico Buti
     */
    private class Writer implements Runnable {

        /** The serialize stream */
        private ObjectOutputStream o;

        /** The file to which write */
        private FileOutputStream fos;

        /** The {@code List} of {@code Command} objects to be stored */
        private ArrayList<Command> commands;

        /**
         * Builds the {@code Writer} thread.
         * 
         * @param o
         *            The {@code stream} to which write.
         * @param commands
         *            The list of {@code Entity} objects evolution.
         */
        Writer(ObjectOutputStream o, FileOutputStream fos,
                ArrayList<Command> commands) {
            this.o = o;
            this.fos = fos;
            this.commands = commands;
        }

        @Override
        public void run() {
            try {
                // TODO forse serve ordinare i comandi!!
                for (Command c : commands) {
                    o.writeObject(c);
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    o.close();
                    fos.close();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
    }

    private void sendSyn(ArrayList<Identifier> peerList) {
        for (int i = 0; i < peerList.size(); i++)
            Messages.sendSyn(this, peerList.get(i));
    }

    /**
     * Destroy the current coordinator, i.e. the current thread, not before the
     * notification to the {@code SimulationAgent} controlling the GUI to notify
     * the user.
     */
    private void terminate() {
        Messages.sendAck(this, inputId);
    }

    private double calculateMinRadius() {
        double minimum = Double.POSITIVE_INFINITY;
        for (ProtoEntity p : xmlInputFile.getProtos())
            if (p.getradius() < minimum)
                minimum = p.getradius();
        return minimum;
    }

    /**
     * Implements {@code BioShape} MAIN LOOP according to
     * "A multiscale meta-model for BioShape" paper. Obviously the method
     * contains synchronisation tasks for ensure the distributed computation
     */
    public void simulationMainLoop() {
        time = 0;
        int iteration = 0;
        while (!eq.isSatisfied(new Double(time))) {
            // request to the Simulation agent to start first iteration
            HermesLogger.log(HermesLogger.INFO, getClass().getSimpleName()
                    + " - " + "Starting iteration #" + ++iteration
                    + " Contatting locals...");
            Messages.sendSynAck(this, inputId);
            if (Messages.receiveSynAck(this, inputId) == null)
                return; // different message --> request for termination
            System.out.println("Total simulated time " + time);
            if (!startNextInteration()) {
                terminate();
                return;
            }
            HermesLogger.log(HermesLogger.INFO, this.getAgentName()
                    + " - iteration correctly started...");
            if (!receiveLocalSteps()) {
                terminate();
                return;
            }
            Identifier id = selectCoordinator();

            // Debug code
            Debug.lastSimulatedTime = time;

            // Update global simulated time
            time += (steps.get(id).doubleValue() < Double.POSITIVE_INFINITY) ? steps
                    .get(id).doubleValue() : delta;

            // Debug code
            Debug.diffLastCurrentSimulatedTime = time - Debug.lastSimulatedTime;
            if (Debug.diffLastCurrentSimulatedTime < 1.0E-3) {
                Debug.countSteps++;
            } else {
                Debug.countSteps = 0;
            }
            //
            if (!advance(id)) {
                terminate();
                return;
            }
            if (!waitInterationTermination()) {// SYNCH & LOGS
                terminate();
                return;
            }
            // TODO LETTURA O_i
            HermesLogger.log(HermesLogger.INFO, getClass().getSimpleName()
                    + " - " + "Ending iteration #" + iteration);
        }
        HermesLogger.log(HermesLogger.INFO, getClass().getSimpleName() + " - "
                + "Equilibrium condition satisfied! Finalizing the simulation"
                + "...");
        delta = Double.NEGATIVE_INFINITY;
        startNextInteration(); // stop LCs
        closeLog();
        // TODO LETTURA O_f
        Messages.sendAck(this, inputId);
    }

    /**
     * Waits for acks from the {@code LocalCoordinator}s to ensure that each
     * simulation node terminated the current timestep operations.
     */
    @SuppressWarnings("unchecked")
    private boolean waitInterationTermination() {
        int acks = 0;
        ObjectOutputStream o = null;
        FileOutputStream fos = null;
        // synchronized (Writer.class) {
        try {
            fos = new FileOutputStream(simFile, true);
            o = new AppendableObjectOutputStream(fos);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        // }
        ArrayList<Command> commands = new ArrayList<Command>();
        // TODO fare con IDS e error
        while (acks != steps.size()) {
            Message<?> msg = Messages.receivedInteraction(this,
                    LocalCoordinator.class, StepProtocol.INTERACTIONEND);
            if (msg == null) {
                HermesLogger.log(HermesLogger.ERROR, this.getAgentName()
                        + " something wrong with the communication "
                        + "with the Locals...Simulation stopped!");
                try {
                    if (o != null)
                        o.close();
                    if (fos != null)
                        fos.close();

                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                return false;
            }
            InteractionContainer ic = (InteractionContainer) msg.getObject();
            ArrayList<Command> cmd = (ArrayList<Command>) ic
                    .getParameter("Log");
            commands.addAll(cmd);
            acks++;
        }
        // new Writer(o, fos, commands).run();
        try {
            if (o == null || fos == null)
                return false;
            for (Command c : commands) {
                o.writeObject(c);
                // TODO Mettere meglio questo controllo di debug
                // System.out.println("Command: " + c);
            }
            fos.close();
            o.close();
        } catch (IOException e) {
            return false;
        }
        return true;
    }

    /**
     * Sends the real step time to the {@code LocalCoordinator}s, so that they
     * can synchronise over it and advance in the simulation.
     */
    private boolean advance(Identifier id) {
        InteractionContainer ic = new InteractionContainer(new StepProtocol());
        Object[] param = new Object[2];
        param[0] = new Double(steps.get(id).doubleValue());
        param[1] = id;
        ic.makeInteraction(StepProtocol.INTERACTIONACKSTEP, param);
        for (Identifier i : steps.keySet()) {
            boolean status = Messages.sendInteraction(this, i, ic);
            if (!status) {
                HermesLogger.log(HermesLogger.ERROR, this.getAgentName()
                        + " something wrong with the communication"
                        + " with the LocalCoordinators");
                return false;
            }
        }
        return true;
    }

    /**
     * Computes the smallest time step among the {@code LocalCoordinator}s
     * passed times. Such times can be {@code ftc}, {@code fts} or {@code delta}
     * values whether the earlier event on the specific {@code LocalCoordinator}
     * is a contact, a splitting or none of the previous, respectively,
     * 
     * @return The earliest time between the times passed by
     *         {@code LocalCoordinator}s or {@code Double#POSITIVE_INFINITY} if
     *         nothing happened during the current timestep
     */
    private Identifier selectCoordinator() {
        Identifier id = null;
        double step = Double.POSITIVE_INFINITY;
        for (Identifier i : steps.keySet()) {
            if (steps.get(i).doubleValue() < step)
                id = i;
        }
        return id;
    }

    // /**
    // * Guarantees that the communication among the simulation nodes happens on
    // a
    // * regular basis.
    // */
    // private void activateSpaceManager() {
    // InteractionContainer ic = new InteractionContainer(synProtocol);
    // Object[] param = new Object[1];
    // param[0] = "";
    // ic.makeInteraction("Syn", param);
    // System.out.println("TriggerSpace");
    // try {
    // sendMessageToUserAgent(new Message<InteractionContainer>(
    // getIdentificator(), spaceId, ic));
    // } catch (CommunicationException e) {
    // e.printStackTrace();
    // }
    //
    // }

    /**
     * Sends a "startIteration" message to each {@code LocalCoordinator} so that
     * the next simulation step can occur. If the flag {@code dynamic} is set
     * the next delta is recalculated dynamically.
     * 
     */
    private boolean startNextInteration() {
        // TODO Possibly the delta can be dynamically recalculated
        InteractionContainer ic = new InteractionContainer(new StepProtocol());
        Object[] params = new Object[1];
        params[0] = new Double(delta);
        ic.makeInteraction(StepProtocol.INTERACTIONSTART, params);
        return sendSynchToLocals(ic);
    }

    /**
     * Receives all the {@code LocalCoordinator}s time steps to find the earlier
     * time during the current timestep.
     */
    private boolean receiveLocalSteps() {
        int counter = steps.size();
        while (counter > 0) {
            // TODO fare con ids e error
            Message<?> msg = Messages.receivedInteraction(this,
                    LocalCoordinator.class, StepProtocol.INTERACTIONSENDSTEP);
            if (msg != null) {
                InteractionContainer ic = (InteractionContainer) msg
                        .getObject();
                steps.put(msg.getSenderAgentId(),
                        (Double) ic.getParameter("Step"));
                counter--;
            } else
                return false;
        }
        return true;
    }

    /**
     * Sends the information container to the {@code LocalCoordinator}s. Waits
     * for a response by each {@code LocalCoordinator}.
     * 
     * @param ic
     *            The information to send to the {@code LocalCoordinator}s
     */
    private boolean sendSynchToLocals(InteractionContainer ic) {
        for (Identifier i : steps.keySet()) {
            if (!Messages.sendInteraction(this, i, ic))
                return false;
            // TODO da fare con l'id!!
            if (Messages.receiveAck(this, LocalCoordinator.class) == null)
                return false;
            HermesLogger.log(HermesLogger.INFO, this.getAgentName()
                    + " - ack received for LocalCoordinator " + i.getName()
                    + "...");
        }
        return true;
    }

    /**
     * Waits messages from objects of the passed class for a given timeout. If
     * the number of object sending message is less than the required, prompts
     * the user whether to wait another timeout or not. Used for the
     * {@code PeerAgent}s and the {@code LocalCoordinator}s which will take part
     * in the simulation.
     * 
     * @see PeerAgent LocalCoordinator
     * 
     * @param from
     *            class of the object sending messages to the GlobalCoordinator
     * @param list
     *            it will contain the object which already sent communications
     * @param numAck
     *            the number of communicating object expected
     * @param timeout
     *            max time to wait for communications from object of the type
     *            passed in{@code from} parameter.
     */
    private void receiveAckWithTimeout(Class<?> from,
            ArrayList<Identifier> list, int numAck, long timeout)
            throws InterruptedException {
        ExecutorService executor = Executors.newSingleThreadExecutor();
        Task acks = new Task(from, list, numAck, new SynProtocol());
        while (true) {
            List<Future<String>> result = executor.invokeAll(
                    Arrays.asList(acks), timeout, TimeUnit.MILLISECONDS);
            if (!result.get(0).isCancelled())
                break; // all acks received...NO cancellation = completion
            // else...
            HermesLogger.log(HermesLogger.WARNING, this.getAgentName()
                    + " - Reached timeout without enough peers "
                    + "communications...");
            // no peers...
            if (list.size() == 0) { // ask the user...
                int k = JOptionPane.showConfirmDialog(null,
                        "Zero " + from.getSimpleName()
                                + "s are arrived, do you want "
                                + "to continue trying the " + "connection "
                                + "? (press 'NO' to abort)", "stop connection",
                        JOptionPane.YES_NO_OPTION);
                if (k == JOptionPane.NO_OPTION
                        || k == JOptionPane.DEFAULT_OPTION) {
                    termination = true;
                    return;
                }
                HermesLogger.log(HermesLogger.INFO, this.getAgentName()
                        + " - Waiting additional time for acks...");
                continue;
            }
            // if at least ONE peer arrived the simulation can be started!
            int k = JOptionPane.showConfirmDialog(null,
                    "Do you want to start the" + " simulation" + " with "
                            + list.size() + " " + from.getSimpleName()
                            + "? (press 'NO' to abort, 'CANCEL'"
                            + "to continue trying the connection)",
                    "stop connection", JOptionPane.YES_NO_CANCEL_OPTION);
            if (k == JOptionPane.NO_OPTION || k == JOptionPane.DEFAULT_OPTION) {
                termination = true;
                return;
            } else if (k == JOptionPane.YES_OPTION)
                break;
            // else continue...
        }
        executor.shutdownNow();
    }

    private class Task implements Callable<String> {
        private int numAck;

        private ArrayList<Identifier> list;

        private Class<?> from;

        private SynProtocol innersynProtocol;

        public Task(Class<?> from, ArrayList<Identifier> list, int numAck,
                SynProtocol synProtocol) {
            this.from = from;
            this.list = list;
            this.numAck = numAck;
            innersynProtocol = synProtocol;
        }

        /**
         * Accepts messages from {@code PeerAgent}s or {@code LocalCoordinator}s
         * until the desired number has been reached. Can be stopped if
         * {@code timeout
         * is reached}.
         * 
         * @see GlobalCoordinator#receiveAckWithTimeout(Class, ArrayList, int,
         *      long)
         */
        @Override
        public String call() {
            while (list.size() < numAck) {
                Message<?> msg = getMessageSynch(from.getSimpleName());
                InteractionContainer ic = (InteractionContainer) msg
                        .getObject();
                // case 1: not syn or bad interaction
                if (!ic.validate(innersynProtocol)
                        || !ic.getInteractionName().equals(
                                SynProtocol.INTERACTIONACK)) {
                    HermesLogger.log(HermesLogger.WARNING,
                            GlobalCoordinator.this.getAgentName()
                                    + " protocol error while "
                                    + "communicating with the GLOBAL");
                    continue;
                }
                // case 2: everything OK!! Check Identificator presence...
                if (!list.contains(msg.getSenderAgentId())) {
                    list.add(msg.getSenderAgentId());
                    HermesLogger.log(HermesLogger.INFO,
                            GlobalCoordinator.this.getAgentName()
                                    + " Received a new " + from.getSimpleName()
                                    + "...");
                } else
                    HermesLogger.log(HermesLogger.WARNING,
                            GlobalCoordinator.this.getAgentName()
                                    + " Identificator already received..."
                                    + "no overwriting...");
            }
            return "finished";
        }
    }

    /**
     * Increase simulation global time of the {@code delta} time elapsed in the
     * current iteration.
     * 
     * @param delta
     *            the current timestep
     */
    public void updateTimer(double delta) {
        time += delta;
    }

    /**
     * Metodo che restituisce il valore del timer.
     * 
     * @return globalTimer: valore attuale del timer globale
     */
    public double getTimer() {
        return time;
    }

    /**
     * Returns the best delta timestep for the next iteration, given the max
     * radius of a shape and the maximal velocity available in the simulator.
     * 
     * @param maxTranslation
     *            Maximal linear translation possible
     * @param maxRadius
     *            Max radius of the shapes
     */
    public void computeDelta(double maxTranslation, double maxRadius) {
        this.delta = minRadius / maxTranslation; // TODO verificare
    }

    /**
     * Provides a workaround for the {@code StreamCorruptedException}
     * "invalid type code: AC". Basically, in the standard Java library, it is
     * not expected that a serialized stream is appended. Indeed, each time a
     * file is opened to be written a header is inserted in the file, causing an
     * error while reading the file afterwards.<br>
     * This class overrides the default behaviour, avoiding to write the header.
     * Obviously, the first time the file is created, standard Java library must
     * be instanced (we need the header at the beginning).
     * 
     * @author Federico Buti
     */
    private class AppendableObjectOutputStream extends ObjectOutputStream {

        public AppendableObjectOutputStream(OutputStream out)
                throws IOException {
            super(out);
        }

        @Override
        protected void writeStreamHeader() throws IOException {
            // DOES NOT WRITE THE FILE HEADER
        }

    }
}